
import java.nio.*;
import java.nio.charset.*;
import java.nio.channels.*;
import java.util.*;
import java.net.*;
import java.io.*;

import javax.vecmath.Vector3f;

/**
 * ChatterClient.java
 *
 * A basic example of a multi-user chat application using the JDK 1.4 NIO libraries
 * 
 * @author <a href="mailto:bret@hypefiend.com">bret barker</a>
 * @version 1.0
 */
public class ClientNIO extends Thread {
    private static final int BUFFER_SIZE = 255;
    private static final long CHANNEL_WRITE_SLEEP = 100L;
    private static final int PORT = 12345;
  
    private ByteBuffer writeBuffer;
    private ByteBuffer readBuffer;
    private boolean running;
    private SocketChannel channel;
    private String host;
    private Selector readSelector;
    private CharsetDecoder asciiDecoder;
    private InputThread it;
    Thread hitSound=new Sound("hit.wav",50);
    Thread spreeSound=new Sound("hit.wav",70);
    boolean firstBlood=false;
    String recvPosX = new String();

	String recvPosY = new String();
	String recvPosZ = new String();
	String recvState = new String();
	String recvPlayerID = new String();
	String gamePosition = new String();
	String recvRotX = new String();
	boolean isRunning = true;



	  String recvKillerID = new String();
	  String myID = "0";
   
    public ClientNIO(String host) {
	this.host = host;
	writeBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
	readBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
	asciiDecoder = Charset.forName( "US-ASCII").newDecoder();;
	this.start();
    }

    public void run() {

    	System.out.println("trying to connect");

	connect(host);
	
	it = new InputThread(this);
	it.start();
	
	running = true;
	while (running && !this.isInterrupted()) {
		
		
	    readIncomingMessages();

	    // nap for a bit
	    try {
		Thread.sleep(10);
	    }
	    catch (InterruptedException ie) {
	    }
	}
   
    }

    private void connect(String hostname) {
	try {
	    readSelector = Selector.open();
	    InetAddress addr = InetAddress.getByName(hostname);
	    channel = SocketChannel.open(new InetSocketAddress(addr, PORT));
	    channel.configureBlocking(false);
	    channel.register(readSelector, SelectionKey.OP_READ, new StringBuffer());
	}
	catch (UnknownHostException uhe) {
	}
	catch (ConnectException ce) {
	}
	catch (Exception e) {
	}
    }
    
    private synchronized void readIncomingMessages() {
	// check for incoming mesgs
	try {
	    // non-blocking select, returns immediately regardless of how many keys are ready
	    readSelector.selectNow();
		
	    // fetch the keys
	    Set readyKeys = readSelector.selectedKeys();
		
	    // run through the keys and process
	    Iterator i = readyKeys.iterator();
	    while (i.hasNext()) {
		SelectionKey key = (SelectionKey) i.next();
		i.remove();
		SocketChannel channel = (SocketChannel) key.channel();
		readBuffer.clear();
		
		// read from the channel into our buffer
		long nbytes = channel.read(readBuffer);

		// check for end-of-stream
		if (nbytes == -1) { 
		    System.out.println("disconnected from server: end-of-stream");
		    channel.close();
		    shutdown();
		    it.shutdown();
		}
		else {
		    // grab the StringBuffer we stored as the attachment
		    StringBuffer sb = (StringBuffer)key.attachment();

		    // use a CharsetDecoder to turn those bytes into a string
		    // and append to our StringBuffer
		    readBuffer.flip( );
		    String str = asciiDecoder.decode( readBuffer).toString( );
		    sb.append( str );
		    readBuffer.clear( );

		    // check for a full line and write to STDOUT
		    String line = sb.toString();
		    line = line.trim();
		    sb.delete(0,sb.length());
		    StringTokenizer st = new StringTokenizer(line);
		    //System.out.println(line);
		    if(line.startsWith("ID")) {
		    	st.nextToken();
		    	myID = st.nextToken();
		    	myID = myID.substring(0, 1);
		    	SSG.modelMD2.id = myID;
		    	System.out.println("My ID is " + myID);		    	
		    }
		    
		    if(line.startsWith("RESPAWNED") == true) {
		    	st.nextToken();
		    	String playerRespawnedID = st.nextToken();
		    	Player p = (Player)SSG.playerList.get(playerRespawnedID);
		    	p.isKilled = false;
		    	p.m_rX = 0;
		    	SSG.killAngle = 0;
		    }
		    
		    if(line.startsWith("HIT")) {
		    	st.nextToken();
		    	
		    	String playerHitIDRecv = st.nextToken();
		    	String playerHitByIDRecv = st.nextToken();
		    	//System.out.println("HIT RECIEVED " + playerHitIDRecv);
		    	//System.out.println("MY ID:" + myID + " HIT ID " + playerHitIDRecv);
		    	int intHitID = Integer.parseInt(playerHitIDRecv);
		    	int intMyID = Integer.parseInt(myID);
		    System.out.println("HIT " + playerHitIDRecv);
		    //System.out.println("HIT " + playerHitIDRecv);
		    	//System.out.println(intHitID + " " + intMyID);
		    	if(intHitID == intMyID) {
		    		if(!hitSound.isAlive()){
		    			hitSound=new Sound("hit.wav",50);
				    }
				    try{	
		        		hitSound.start();		
		        	}
		        	catch(IllegalThreadStateException e){
		        		//e.printStackTrace();
		        	}
		    		SSG.modelMD2.health -= 50;
		    		//System.out.println("Health -10");
		    		if(SSG.modelMD2.health ==0){
		    			SSG.modelMD2.isKilled=true;	
		    			SSG.modelMD2.killedByID=playerHitByIDRecv;
		    		}
		    	}
		    }
		    
		    
		    if(line.startsWith("DIED") == true) {
		    	st.nextToken();
		    	recvPlayerID = st.nextToken();	
		    	recvPlayerID = recvPlayerID.substring(0, 1);
		    	recvKillerID = st.nextToken();
		    	recvKillerID = recvKillerID.substring(0, 1);
		    	System.out.println("DIED " + recvKillerID);
		    	if(SSG.playerList.containsKey(recvPlayerID)){	//we are not in hashmap, so we dont set that we are killed
			    	Player p = (Player)SSG.playerList.get(recvPlayerID);
			    	p.isKilled = true;
		    	}
		    	

		    	if(recvKillerID.equals(myID) == false){
		    		Player p2  = (Player)SSG.playerList.get(recvKillerID);
			    	p2.score++;
		    	}
		    	
		    	if(recvKillerID.equals(myID) == true) {
		    		SSG.modelMD2.score++;
		    		SSG.modelMD2.spree++;
		    	}

	    	
		    	if(recvPlayerID.equals(myID) == true){

		    		SSG.modelMD2.spree=0;
		    	}
		    	
		    	try{
			    	switch(SSG.modelMD2.spree){
			    	case 0:
			    		break;
			    	case 1:
			    		break;
			    	case 2:
			    		break;
			    	case 3:
			    		spreeSound=new Sound("Killing_spree.wav",70);
			    		spreeSound.start();
			    		break;
			    	case 4:
			    		spreeSound=new Sound("Dominating.wav",70);
			    		spreeSound.start();
			    		break;
			    	case 5:
			    		spreeSound=new Sound("MegaKill.wav",70);
			    		spreeSound.start();
			    		break;
			    	case 6:
			    		spreeSound=new Sound("Unstoppable.wav",70);
			    		spreeSound.start();
			    		break;
			    	case 7:
			    		spreeSound=new Sound("WhickedSick.wav",70);
			    		spreeSound.start();
			    		break;
			    	case 8:
			    		spreeSound=new Sound("monster_kill.wav",70);
			    		spreeSound.start();
			    		break;
			    	case 9:
			    		spreeSound=new Sound("GodLike.wav",70);
			    		spreeSound.start();
			    		break;
			    	default:
			    		spreeSound=new Sound("HolyShit.wav",70);
			    		spreeSound.start();	
			    		break;
			    	}
		    	}
		    	catch(IllegalThreadStateException e){
	        		//e.printStackTrace();
	        	}
		    	
		    	Player p3=(Player)SSG.playerList.get(recvKillerID);
		    	Player p4=(Player)SSG.playerList.get(recvPlayerID);
		    	
		    	SSG.message=p3.nick+" killed "+p4.nick;
		    	SSG.messageBoolean=true;
		    	
		    }
		    
		    if(line.startsWith("POS")) {
		    
		    while(st.hasMoreTokens()) {
		    	st.nextToken();
		    	recvPlayerID = st.nextToken();
		    				
		    	recvPosX = st.nextToken();
		    	recvPosY = st.nextToken();
		    	recvPosZ = st.nextToken();
		    	recvRotX = st.nextToken();
		    	recvState = st.nextToken();
		    	if(recvPlayerID.contains(myID)) continue;
		    	if(SSG.playerList.containsKey(recvPlayerID) == true) {
		    		// Update player position
		    		Player player = (Player) SSG.playerList.get(recvPlayerID);
		    		float a = (float)Integer.parseInt(recvPosX);
					float b = (float)Integer.parseInt(recvPosY);
					float c = (float)Integer.parseInt(recvPosZ);
					float d = (float)Integer.parseInt(recvRotX);
					//System.out.println(a + " " + b + " " + c);
		    		//player.setPosition( (float)Integer.parseInt(recvPosX), (float)Integer.parseInt(recvPosY), (float)Integer.parseInt(recvPosZ));
		    		player.setPosition(a,b,c);
		    		player.m_rY = d;
		    		player.state = Integer.parseInt(recvState);
		    	}
		    	
		    	else {
		    		// Create new player and add it to playerList hashmap
		    		System.out.println("CREATING NEW PLAYER " + recvPlayerID);
		    		Player player = new Player(new Vector3f(0f,33f,0f));	//create player and set its position
					player.model = player.getPhyModel();	//load physics model, for later use
					player.modelName = "WalkMech.md2";
					player.state = 0;
//					System.out.println(recvPosX + " " + recvPosY + " " + recvPosZ);
//					System.out.println(recvPlayerID);
//					System.out.println(Integer.parseInt(recvPosX));
					
		    		//player.setPosition(a,b,c);
		    		//System.out.println(recvPlayerID);
		    		player.loadedTextures = false;
			    	
		    		SSG.playerList.put(recvPlayerID, player);
		    		System.out.println("player added in hashmap");
		    	}
		    
		    }
		    }
		   
		    }
		}
	    }		
	
	catch (IOException ioe) {
	}
	catch (Exception e) {
	}
    }
    
    private void sendMessage(String mesg) {
	prepWriteBuffer(mesg);
	channelWrite(channel, writeBuffer);
    }

    private void prepWriteBuffer(String mesg) {
	// fills the buffer from the given string
	// and prepares it for a channel write
	writeBuffer.clear();
	writeBuffer.put(mesg.getBytes());
	writeBuffer.putChar('\n');
	writeBuffer.flip();
    }
    
    
    private void channelWrite(SocketChannel channel, ByteBuffer writeBuffer) {
	long nbytes = 0;
	long toWrite = writeBuffer.remaining();

	// loop on the channel.write() call since it will not necessarily
	// write all bytes in one shot
	try {
	    while (nbytes != toWrite) {
		nbytes += channel.write(writeBuffer);
		
		try {
		    Thread.sleep(CHANNEL_WRITE_SLEEP);
		}
		catch (InterruptedException e) {}
	    }
	}
	catch (ClosedChannelException cce) {
	}
	catch (Exception e) {
	} 
	
	// get ready for another write if needed
	writeBuffer.rewind();
    }

    public void shutdown() {
	running = false;
	interrupt();
    }

    /** 
     * InputThread reads user input from STDIN
     */
    class InputThread extends Thread {
	private ClientNIO cc;
	public InputThread(ClientNIO cc) {
	    this.cc = cc;
	}

	public void run() {
		while(!this.isInterrupted()) {
			long currentTime = System.nanoTime();
			if(SSG.playerHitID.equals("-1") == false) {
				Player p = (Player)SSG.playerList.get(SSG.playerHitID);

				long time = 1000000000L;

				if( (currentTime - p.lastHitTime) >= time) {
				p.lastHitTime = currentTime;
				String playerHitMSG = "HIT " + SSG.playerHitID + " " + myID;
				cc.sendMessage(playerHitMSG);
				
				}
				SSG.playerHitID = "-1";
			}
			
			if(SSG.modelMD2.isKilled == true && SSG.modelMD2.messageSent == false) {
				String playerDeadMSG = "DIED " +myID + " " + SSG.modelMD2.killedByID;
				cc.sendMessage(playerDeadMSG);
				SSG.modelMD2.messageSent = true;
			}
			
			if(SSG.modelMD2.respawned == true) {
				cc.sendMessage("RESPAWNED " + myID);
				SSG.modelMD2.respawned = false;

				SSG.modelMD2.messageSent=false;

			}
			String ourPosition = "POS " + myID + " " + Integer.toString((int)SSG.modelMD2.m_nX) + " " + Integer.toString((int)SSG.modelMD2.m_nY) + " " + Integer.toString((int)SSG.modelMD2.m_nZ) + " " + Integer.toString((int)SSG.cursorX) + " " + Integer.toString(SSG.modelMD2.state);
			
			cc.sendMessage(ourPosition);
		}
		
	}
	public void shutdown() {
		interrupt();
	}
    }
}
